﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace Model
{
    [ObjectEvent]
    public class SessionEvent : ObjectEvent<Session>, IAwake<TChannel,NetworkComponent>,IStart
    {
        public void Awake(TChannel channel, NetworkComponent network)
        {
            this.Get().Awake(channel, network);
        }

        public void Start()
        {
            this.Get().Start();
        }
    }

    /// <summary>
    /// 一个会话
    /// </summary>
    public sealed class Session : Entity, IAwake<TChannel, NetworkComponent>
    {
        private static uint RpcId { get; set; }
        private TChannel channel;
        private NetworkComponent network;
        private readonly List<byte[]> byteses = new List<byte[]>() { new byte[0], new byte[1] };
        private readonly Dictionary<uint, Action<object>> reqeustCallback = new Dictionary<uint, Action<object>>();
        public IPEndPoint RemoteAddress
        {
            get
            {
                return this.channel.RemoteAddress;
            }
        }

        public void Awake(TChannel channel, NetworkComponent network)
        {
            this.channel = channel;
            this.network = network;
        }

        public void Start()
        {
            this.StartRecv();
        }

        /// <summary>
        /// 开始接受并解析客户端发来的消息
        /// </summary>
        private async void StartRecv()
        {
            while (true)
            {
                if(this.Id == 0)
                {
                    return;
                }

                Packet packet;
                try
                {
                    packet = await this.channel.Recv(); // 获取一个完整数据包
                    if (this.Id == 0)
                        return;
                }
                catch (Exception e)
                {
                    Log.Error(e.Message);
                    continue;
                }

                if(packet.Length < 2)   // 完整数据包，不够基本长度。
                {
                    Log.Error($"message error length < 2 ip:{this.RemoteAddress}");
                    this.network.Remove(this.Id);
                    return;
                }

                // 获取消息操作码
                ushort opcode = BitConverter.ToUInt16(packet.Bytes, 0);

                // 偏移2字节，是因为opcode占2字节
                this.RunDecompressedBytes(opcode, packet.Bytes, 2, packet.Length);
            }
        }

        /// <summary>
        /// 开始解析消息，并分发下去。
        /// </summary>
        /// <param name="opcode">操作码</param>
        /// <param name="messageBytes">消息数据</param>
        /// <param name="offset">偏移</param>
        /// <param name="count">总结</param>
        public void RunDecompressedBytes(ushort opcode,byte[] messageBytes,int offset,int count)
        {
            Opcode op;
            object message = null;
            try
            {
                op = (Opcode)opcode;
                OpcodeTypeComponent opcodeTypeComponent = Game.Scene.GetComponent<OpcodeTypeComponent>();
                Type type = opcodeTypeComponent.GetType(op);
                message = this.network.Packer.DeserializeFrom(type, messageBytes, offset, count - offset);
            }
            catch (Exception e)
            {
                Log.Error($"message deserialize error, ip: {this.RemoteAddress} {opcode} {e}");
                this.network.Remove(this.Id);
                return;
            }

            // 如果是RPC回应消息
            AResponse response = message as AResponse;
            if(response != null)
            {
                if(!this.reqeustCallback.TryGetValue(response.RpcId,out Action<object> action))
                {
                    return;
                }
                this.reqeustCallback.Remove(response.RpcId);
                action(message);
                return;
            }

            this.network.MessageDispatcher.Dispatcher(this, (AMessage)message);
        }

        /// <summary>
        /// RPC调用
        /// </summary>
        /// <typeparam name="Response"></typeparam>
        /// <param name="request"></param>
        /// <returns></returns>
        public Task<Response> Call<Response>(ARequest request) where Response:AResponse
        {
            request.RpcId = ++RpcId;

            var tcs = new TaskCompletionSource<Response>();

            this.reqeustCallback[request.RpcId] = (message) =>
            {
                try
                {
                    Response response = (Response)(message);
                    if(response.Error > 100)
                    {
                        tcs.SetException(new Exception(response.Error +"|"+ response.Message));
                        return;
                    }
                    tcs.SetResult(response);
                }
                catch (Exception e)
                {
                    tcs.SetException(new Exception($"Rpc Error: {typeof(Response).FullName}", e));
                }
            };

            this.SendMessage(request);
            return tcs.Task;
        }

        public void Send(AMessage message)
        {
            if(this.Id == 0)
            {
                throw new Exception("session已经被Dispose了");
            }
            this.SendMessage(message);
        }

        public void Reply<Resp>(Resp resp) where Resp:AResponse
        {

            if (this.Id == 0)
            {
                throw new Exception("session已经被Dispose了");
            }
            this.SendMessage(resp);
        }

        private void SendMessage(object message)
        {
            Opcode opcode = Game.Scene.GetComponent<OpcodeTypeComponent>().GetOpcode(message.GetType());
            ushort op = (ushort)opcode;

            byte[] messageBytes = this.network.Packer.SerializeToByteArray(message);

            // 如果是all，就直接转发。不走网络
            if(this.network.AppType == AppType.All)
            {
                Session session = Game.Scene.GetComponent<NetInnerComponent>().Get(this.RemoteAddress.ToString());
                session.RunDecompressedBytes(op, messageBytes, 0, messageBytes.Length);
                return;
            }

            byte[] opcodeBytes = BitConverter.GetBytes(op);

            this.byteses[0] = opcodeBytes;
            this.byteses[1] = messageBytes;
            this.channel.Send(this.byteses);
        }

        public override void Dispose()
        {
            if (this.Id == 0)
                return;

            long id = this.Id;
            base.Dispose();
            this.channel.Dispose();
        }
    }
}
